#!/bin/bash
set -e

# PairDrop version when this file was last changed
version="v1.10.4"

############################################################
# Help                                                     #
############################################################
help()
{
   # Display Help
   echo "Send files or text with PairDrop via command-line interface."
   echo "Current domain: ${DOMAIN}"
   echo
   echo "Usage:"
   echo -e "Open PairDrop:\t\t$(basename "$0")"
   echo -e "Send files:\t\t$(basename "$0") file1/directory1 (file2/directory2 file3/directory3 ...)"
   echo -e "Send text:\t\t$(basename "$0") -t \"text\""
   echo -e "Specify domain:\t\t$(basename "$0") -d \"https://pairdrop.net/\""
   echo -e "Show this help text:\t$(basename "$0") (-h|--help)"
   echo
   echo "This pairdrop-cli version was released alongside ${version}"
}

openPairDrop()
{
  url="$DOMAIN"
  if [[ -n $params ]];then
    url="${url}?${params}"
  fi
  if [[ -n $hash ]];then
    url="${url}#${hash}"
  fi

  echo "PairDrop is opening at $DOMAIN"
  if [[ $OS == "Windows" ]];then
    start "$url"
  elif [[ $OS == "Mac" ]];then
    open "$url"
  elif [[ $OS == "WSL" || $OS == "WSL2" ]];then
    powershell.exe /c "Start-Process ${url}"
  else
    xdg-open "$url" > /dev/null 2>&1
  fi


  exit

}

setOs()
{
  unameOut=$(uname -a)
  case "${unameOut}" in
      *Microsoft*)     OS="WSL";; #must be first since Windows subsystem for linux will have Linux in the name too
      *microsoft*)     OS="WSL2";; #WARNING: My v2 uses ubuntu 20.4 at the moment slightly different name may not always work
      Linux*)          OS="Linux";;
      Darwin*)         OS="Mac";;
      CYGWIN*)         OS="Cygwin";;
      MINGW*)          OS="Windows";;
      *Msys)           OS="Windows";;
      *)               OS="UNKNOWN:${unameOut}"
  esac
}

specifyDomain()
{
  [[ ! $1 = http* ]] || [[ ! $1 = */ ]] && echo "Incorrect format. Specify domain like https://pairdrop.net/" && exit
  echo "DOMAIN=${1}" > "$config_path"
  echo -e "Domain is now set to:\n$1\n"
}

sendText()
{
    params="base64text=hash"
    hash=$(echo -n "${OPTARG}" | base64)

    if [[ $(echo -n "$hash" | wc -m) -gt 32600 ]];then
      params="base64text=paste"
      if [[ $OS == "Windows" || $OS == "WSL" || $OS == "WSL2" ]];then
        echo -n "$hash" | clip.exe
      elif [[ $OS == "Mac" ]];then
        echo -n "$hash" | pbcopy
      else
        (echo -n "$hash" | xclip) || echo "You need to install xclip for sending bigger files from cli"
      fi
      hash=
    fi

    openPairDrop
    exit
}

escapePSPath()
{
  local path=$1
  
  # escape '[' and ']' with grave accent (`) character
  pathPS=${path//[/\`[}
  pathPS=${pathPS//]/\`]}
  # escape single quote (') with another single quote (')
  pathPS=${pathPS//\'/\'\'}
  
  # Convert GitHub bash path "/i/path" to Windows path "I:/path"
  if [[ $pathPS == /* ]]; then
    # Remove preceding slash
    pathPS="${pathPS#/}"
    # Convert drive letter to uppercase
    driveLetter=$(echo "${pathPS::1}" | tr '[:lower:]' '[:upper:]')
    # Put together absolute path as used in Windows
    pathPS="${driveLetter}:${pathPS:1}"        
  fi
  
  echo "$pathPS"
}

sendFiles()
{
  params="base64zip=hash"
  workingDir="$(pwd)"
  tmpDir="/tmp/pairdrop-cli-temp/"
  tmpDirPS="\$env:TEMP/pairdrop-cli-temp/"
  
  index=0
  directoryBaseNamesUnix=()
  directoryPathsUnix=()
  filePathsUnix=()
  directoryCount=0
  fileCount=0
  pathsPS=""

  #create tmp folder if it does not exist already
  if [[ ! -d "$tmpDir" ]]; then
    mkdir "$tmpDir"
  fi

  for arg in "$@"; do
   echo "$arg"
   [[ ! -e "$arg" ]] && echo "The given path $arg does not exist." && exit

    # Remove trailing slash from directory
    arg="${arg%/}"

    # get absolute path and basename of file/directory
    absolutePath=$(realpath "$arg")
    baseName=$(basename "$absolutePath")
    directoryPath=$(dirname "$absolutePath")

    if [[ -d $absolutePath ]]; then
      # is directory
      ((directoryCount+=1))
      # add basename and directory path to arrays
      directoryBaseNamesUnix+=("$baseName")
      directoryPathsUnix+=("$directoryPath")
    else
      # is file
      ((fileCount+=1))
      absolutePathUnix=$absolutePath
      # append new path and separate paths with space
      filePathsUnix+=("$absolutePathUnix")
    fi

    # Prepare paths for PowerShell on Windows
    if [[ $OS == "Windows" ]];then
      absolutePathPS=$(escapePSPath "$absolutePath")
      
      # append new path and separate paths with commas
      pathsPS+="'${absolutePathPS}', "
    fi
    
    # set fileNames on first loop
    if [[ $index == 0 ]]; then
      baseNameU=${baseName// /_}

      # Prevent baseNameU being empty for hidden files by removing the preceding dot
      if [[ $baseNameU == .* ]]; then
        baseNameU=${baseNameU#.*}
      fi

      # only use trunk of basename "document.txt" -> "document"
      baseNameTrunk=${baseNameU%.*}
      
      # remove all special characters
      zipName=${baseNameTrunk//[^a-zA-Z0-9_]/}
      
      zipToSendAbs="${tmpDir}${zipName}_pairdrop.zip"
      wrapperZipAbs="${tmpDir}${zipName}_pairdrop_wrapper.zip"

      if [[ $OS == "Windows" ]];then
        zipToSendAbsPS="${tmpDirPS}${zipName}_pairdrop.zip"
        wrapperZipAbsPS="${tmpDirPS}${zipName}_pairdrop_wrapper.zip"
      fi
    fi

    ((index+=1)) # somehow ((index++)) stops the script
  done

  # Prepare paths for PowerShell on Windows
  if [[ $OS == "Windows" ]];then
    # remove trailing comma
    pathsPS=${pathsPS%??}
  fi

  echo "Preparing ${fileCount} files and ${directoryCount} directories..."

  # if arguments include files only -> zip files once so files it is unzipped by sending browser
  # if arguments include directories -> wrap first zip in a second wrapper zip so that after unzip by sending browser a zip file is sent to receiver
  #
  # Preferred zip structure:
  # pairdrop "d1/d2/d3/f1" "../../d4/d5/d6/f2" "d7/" "../d8/" "f5"
  # zip structure: pairdrop.zip
  #                |-f1
  #                |-f2
  #                |-d7/
  #                |-d8/
  #                |-f5
  # -> truncate (relative) paths but keep directories
  
  [[ -e "$zipToSendAbs" ]] && echo "Cannot overwrite $zipToSendAbs. Please remove first." && exit

  if [[ $OS == "Windows" ]];then
    # Powershell does preferred zip structure natively
    powershell.exe -Command "Compress-Archive -Path ${pathsPS} -DestinationPath ${zipToSendAbsPS}"
  else
    # Workaround needed to create preferred zip structure on unix systems
    # Create zip file with all single files by junking the path
    if [[ $fileCount != 0 ]]; then
      zip -q -b /tmp/ -j -0 -r "$zipToSendAbs" "${filePathsUnix[@]}"
    fi

    # Add directories recursively to zip file
    index=0
    while [[ $index < $directoryCount ]]; do
      # workaround to keep directory name but junk the rest of the paths

      # cd to path above directory
      cd "${directoryPathsUnix[index]}"

      # add directory to zip without junking the path
      zip -q -b /tmp/ -0 -u -r "$zipToSendAbs" "${directoryBaseNamesUnix[index]}"

      # cd back to working directory
      cd "$workingDir"

      ((index+=1)) # somehow ((index++)) stops the script
    done
  fi

  # If directories are included send as zip
  # -> Create additional zip wrapper which will be unzipped by the sending browser
  if [[ "$directoryCount" != 0 ]]; then
    echo "Bundle as ZIP file..."

    # Prevent filename from being absolute zip path by "cd"ing to directory before zipping
    zipToSendDirectory=$(dirname "$zipToSendAbs")
    zipToSendBaseName=$(basename "$zipToSendAbs")

    cd "$zipToSendDirectory"
    
    [[ -e "$wrapperZipAbs" ]] && echo "Cannot overwrite $wrapperZipAbs. Please remove first." && exit
    
    if [[ $OS == "Windows" ]];then
      powershell.exe -Command "Compress-Archive -Path ${zipToSendBaseName} -DestinationPath ${wrapperZipAbsPS} -CompressionLevel Optimal"
    else
      zip -q -b /tmp/ -0 "$wrapperZipAbs" "$zipToSendBaseName"
    fi
    cd "$workingDir"

    # remove inner zip file and set wrapper as zipToSend (do not differentiate between OS as this is done via Git Bash on Windows)
    rm "$zipToSendAbs"

    zipToSendAbs=$wrapperZipAbs
  fi

  # base64 encode zip file
  if [[ $OS == "Mac" ]];then
    hash=$(base64 -i "$zipToSendAbs")
  else
    hash=$(base64 -w 0 "$zipToSendAbs")
  fi

  # remove zip file (do not differentiate between OS as this is done via Git Bash on Windows)
  rm "$zipToSendAbs"

  if [[ $(echo -n "$hash" | wc -m) -gt 1000 ]];then
    params="base64zip=paste"

    # Copy $hash to clipboard
    if [[ $OS == "Windows" || $OS == "WSL" || $OS == "WSL2" ]];then
      echo -n "$hash" | clip.exe
    elif [[ $OS == "Mac" ]];then
      echo -n "$hash" | pbcopy
    elif [ -n "$WAYLAND_DISPLAY" ]; then
      # Wayland
      if ! command -v wl-copy &> /dev/null; then
          echo -e "You need to install 'wl-copy' to send bigger filePathsUnix from cli"
          echo "Try: sudo apt install wl-clipboard"
          exit 1
      fi
      # Workaround to prevent use of Pipe which has a max letter limit
      echo -n "$hash" > /tmp/pairdrop-cli-temp/pairdrop_hash_temp
      wl-copy < /tmp/pairdrop-cli-temp/pairdrop_hash_temp
      rm /tmp/pairdrop-cli-temp/pairdrop_hash_temp
    else
      # X11
      if ! command -v xclip &> /dev/null; then
          echo -e "You need to install 'xclip' to send bigger filePathsUnix from cli"
          echo "Try: sudo apt install xclip"
          exit 1
      fi
      echo -n "$hash" | xclip -sel c
    fi
    hash=
  fi

  openPairDrop
  exit
}

############################################################
############################################################
# Main program                                             #
############################################################
############################################################
script_path="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"

pushd . > '/dev/null';
script_path="${BASH_SOURCE[0]:-$0}";

while [ -h "$script_path" ];
do
  cd "$( dirname -- "$script_path"; )";
  script_path="$( readlink -f -- "$script_path"; )";
done

cd "$( dirname -- "$script_path"; )" > '/dev/null';
script_path="$( pwd; )";
popd  > '/dev/null';

config_path="${script_path}/.pairdrop-cli-config"

# If config file does not exist, try to create it. If it fails log error message and exit
[ ! -f "$config_path" ] &&
  specifyDomain "https://pairdrop.net/" &&
  [ ! -f "$config_path" ] &&
  echo "Could not create config file. Add 'DOMAIN=https://pairdrop.net/' to a file called .pairdrop-cli-config in the same file as this 'pairdrop' bash file (${script_path})" &&
  exit

# Read config variables
export "$(grep -v '^#' "$config_path" | xargs)"

setOs

############################################################
# Process the input options. Add options as needed.        #
############################################################
# Get the options
# open PairDrop if no options are given
[[ $# -eq 0 ]] && openPairDrop && exit

#  display help and exit if first argument is "--help" or more than 2 arguments are given
[ "$1" == "--help" ] && help && exit

while getopts "d:ht:*" option; do
  case $option in
    d) # specify domain - show help and exit if too many arguments
      [[ $# -gt 2 ]] && help && exit
      specifyDomain "$2"
      exit;;
    t) # Send text - show help and exit if too many arguments
      [[ $# -gt 2 ]] && help && exit
      sendText
      exit;;
    h | ?) # display help and exit
      help
      exit;;
    esac
done

# Send file(s)
sendFiles "$@"
